package com.ideal.manage.esms.jpa;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPADeleteClause;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.querydsl.jpa.impl.JPAUpdateClause;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.QueryUtils;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import static org.springframework.data.jpa.repository.query.QueryUtils.getQueryString;

/**
 * Created by xingsen
 */
public class JpaRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
        implements JpaRepository<T, ID> {
    protected Logger LOGGER = LoggerFactory.getLogger(JpaRepositoryImpl.class);

    private final JpaEntityInformation<T, ID> entityInformation;
    private final EntityManager em;
    private final EntityPath<T> path;
    private final JPAQueryFactory queryFactory;

    public JpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
        this(entityInformation, entityManager, SimpleEntityPathResolver.INSTANCE);
    }

    public JpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
                             EntityPathResolver resolver) {
        super(entityInformation, entityManager, resolver);
        this.entityInformation = entityInformation;
        this.em = entityManager;
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public JpaRepositoryImpl(Class<T> domainClass, EntityManager em) {
        this((JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
    }

    public int deleteInId(Collection<ID> ids) {
        if (ids == null || ids.isEmpty()) {
            return 0;
        }
        String ql = getQueryString(QueryUtils.DELETE_ALL_QUERY_STRING, entityInformation.getEntityName());
        LOGGER.info(ql);
        return applyAndBind(ql, ids, em).executeUpdate();
    }

    public Query applyAndBind(String queryString, Collection<ID> ids, EntityManager entityManager) {
        Assert.notNull(queryString, "queryString is required");
        Assert.notNull(ids, "ids is required");
        Assert.notNull(entityManager, "entityManager is required");

        String alias = QueryUtils.detectAlias(queryString);
        String sb = queryString + " where " +
                String.format(" %s.id IN(?1)", alias);

        Query query = entityManager.createQuery(sb);
        query.setParameter(1, ids);
        return query;
    }

    public Page<T> findAll(DSLPageRequest pageRequest) {
        JPQLQuery countQuery = createQuery(pageRequest.toPredicate());
        JPQLQuery query = createQuery(pageRequest.toPredicate());
        query.offset(pageRequest.getOffset());
        query.limit(pageRequest.getPageSize());

        Long total = countQuery.fetchCount();
        query.orderBy(pageRequest.toOrderSpecifier(path));
        List<T> content = total > pageRequest.getOffset() ? query.fetchAll().fetch() : Collections.<T>emptyList();
        return new PageImpl<T>(content, new PageRequest(pageRequest.getPageNumber(), pageRequest.getPageSize()), total);
    }

    public <RT> Page<RT> findAll(DSLPageRequest pageRequest, Expression<RT> listExpr) {
        JPQLQuery countQuery = createQuery(pageRequest.toPredicate());
        JPQLQuery query = createQuery(pageRequest.toPredicate());
        query.offset(pageRequest.getOffset());
        query.limit(pageRequest.getPageSize());

        Long total = countQuery.fetchCount();
        query.orderBy(pageRequest.toOrderSpecifier(path));
        List<RT> content = total > pageRequest.getOffset() ? query.select(listExpr).fetchAll().fetch() : Collections.<RT>emptyList();
        return new PageImpl<RT>(content, new PageRequest(pageRequest.getPageNumber(), pageRequest.getPageSize()), total);
    }

    public List<T> findAll(DSLRequest request) {
        JPQLQuery query = createQuery(request.toPredicate());
        query.orderBy(request.toOrderSpecifier(path));
        List<T> content = query.fetchAll().fetch();
        return content;
    }

    public List<T> findAll(OrderSpecifier<?>... orderSpecifiers) {
        JPQLQuery query = createQuery();
        query.orderBy(orderSpecifiers);
        return query.fetchAll().fetch();
    }

    public JPAQueryFactory queryFactory() {
        return queryFactory;
    }

    //代理queryFactory
    public JPADeleteClause delete(EntityPath<?> path) {
        return queryFactory.delete(path);
    }

    public <T> JPAQuery<T> select(Expression<T> expr) {
        return queryFactory.select(expr);
    }

    public JPAQuery<Tuple> select(Expression<?>[] exprs) {
        return queryFactory.select(exprs);
    }

    public <T> JPAQuery<T> selectDistinct(Expression<T> expr) {
        return queryFactory.selectDistinct(expr);
    }

    public JPAQuery<Tuple> selectDistinct(Expression<?>[] exprs) {
        return queryFactory.selectDistinct(exprs);
    }

    public JPAQuery<Integer> selectOne() {
        return queryFactory.selectOne();
    }

    public JPAQuery<Integer> selectZero() {
        return queryFactory.selectZero();
    }

    public <T> JPAQuery<T> selectFrom(EntityPath<T> from) {
        return queryFactory.selectFrom(from);
    }

    public JPAQuery<?> from(EntityPath<?> from) {
        return queryFactory.from(from);
    }

    public JPAQuery<?> from(EntityPath<?>[] from) {
        return queryFactory.from(from);
    }

    public JPAUpdateClause update(EntityPath<?> path) {
        return queryFactory.update(path);
    }

    public JPAQuery<?> query() {
        return queryFactory.query();
    }

    public JPAUpdateClause updateClause() {
        return new JPAUpdateClause(em, path);
    }

    public JPADeleteClause deleteClause() {
        return new JPADeleteClause(em, path);
    }
}